/**
 * edurender
 * SoftwareRenderer.cpp
 * (c) 2009 J. Brad Byrd
 */

#include "SoftwareRenderer.h"
#include "RenderState.h"

#include <cmath>
#include <algorithm>

static __forceinline er::Int32 RoundToInt32(float value)
{
	return static_cast<er::Int32>(value + 0.5f);
}

static __forceinline float Round(float value)
{
	return std::floorf(value + 0.5f);
}

static void MakeViewportMatrix(er::UInt32 x, er::UInt32 y, er::UInt32 width, er::UInt32 height,
	float minz, float maxz, D3DXMATRIX *out)
{
	float fx = static_cast<float>(x);
	float fy = static_cast<float>(y);
	float fhwidth = static_cast<float>(width) * 0.5f;
	float fhheight = static_cast<float>(height) * 0.5f;
	
	out->_11 = fhwidth;       out->_12 = 0.0f;           out->_13 = 0.0f;         out->_14 = 0.0f;
	out->_21 = 0.0f;          out->_22 = -fhheight;      out->_23 = 0.0f;         out->_24 = 0.0f;
	out->_31 = 0.0f;          out->_32 = 0.0f;           out->_33 = maxz - minz;  out->_34 = 0.0f;
	out->_41 = fx + fhwidth;  out->_42 = fy + fhheight;  out->_43 = minz;         out->_44 = 1.0f;
}

er::SoftwareRenderer *er::SoftwareRenderer::Create(er::UInt32 width, er::UInt32 height)
{
	SoftwareRenderer *renderer = new SoftwareRenderer(width, height);
	
	if (!renderer->Initialize())
	{
		delete renderer;
		renderer = 0;
	}
	
	return renderer;
}

void er::SoftwareRenderer::Destroy(er::SoftwareRenderer *renderer)
{
	delete renderer;
}

void er::SoftwareRenderer::Resize(er::UInt32 width, er::UInt32 height)
{
	m_width = width;
	m_height = height;
	m_widthf = static_cast<float>(width);
	m_heightf = static_cast<float>(height);
	
	m_colorBuffer.resize(width * height);
	m_depthBuffer.resize(width * height);	
}

void er::SoftwareRenderer::GetSize(er::UInt32 *width, er::UInt32 *height) const
{
	if (width != 0)
		*width = m_width;
		
	if (height != 0)
		*height = m_height;
}

void er::SoftwareRenderer::SetColorMask(er::Color mask)
{
	m_colorMask = mask;
}

void er::SoftwareRenderer::ClearColorBuffer(er::Color color)
{
	for (size_t i = 0; i < m_colorBuffer.size(); ++i)
		m_colorBuffer[i] = color;
}

void er::SoftwareRenderer::ClearDepthBuffer(float value)
{
	for (size_t i = 0; i < m_depthBuffer.size(); ++i)
		m_depthBuffer[i] = value;
}

void er::SoftwareRenderer::SetPositionPointer(er::UInt32 size,
	er::UInt32 byteStride, const float *positions)
{
	m_positionSize = size;
	m_positionStride = byteStride;
	m_positions = positions;
}

void er::SoftwareRenderer::SetNormalPointer(er::UInt32 byteStride,
	const float *normals)
{

}

void er::SoftwareRenderer::SetTexCoordPointer(er::UInt32 slot,
	er::UInt32 size, UInt32 byteStride, const float *texCoords)
{

}

void er::SoftwareRenderer::Draw(er::Primitive primitive,
	er::UInt32 vertexCount, er::UInt32 vertexOffset,
	const er::RenderState &renderState)
{
	if (m_positions == 0)
		return;
	
	D3DXMATRIX wvp = renderState.GetWorldMatrix() * renderState.GetViewMatrix() *
		renderState.GetProjectionMatrix();
	D3DXMATRIX viewport;
	MakeViewportMatrix(0, 0, m_width, m_height, 0.0f, 1.0f, &viewport); 
	
	TransformVertices(vertexCount, vertexOffset, wvp * viewport);

	switch (primitive)
	{
		case PRIMITIVE_POINTS:
			DrawPoints(vertexCount);
			break;
		
		case PRIMITIVE_TRIANGLES:
		{
			for (UInt16 i = 0; i < vertexCount; i += 3)
				DrawTriangle(i, i + 1, i + 2);
			
			break;
		}
	}
}

/*
void er::SoftwareRenderer::DrawIndexed(er::Primitive primitive,
	er::UInt32 indexCount, const er::UInt16 *indices,
	const er::RenderState &renderState)
*/

void er::SoftwareRenderer::DrawIndexed(er::Primitive primitive,
	er::UInt16 minIndex, er::UInt16 maxIndex, er::UInt16 indexCount,
	const er::UInt16 *indices, const RenderState &renderState)
{
	if (m_positions == 0 || indices == 0)
		return;
	
	D3DXMATRIX wvp = renderState.GetWorldMatrix() * renderState.GetViewMatrix() *
		renderState.GetProjectionMatrix();
	D3DXMATRIX viewport;
	MakeViewportMatrix(0, 0, m_width, m_height, 0.0f, 1.0f, &viewport); 
	
	//TransformVertices(vertexCount, 0, wvp * viewport);
	UInt16 vertexCount = maxIndex - minIndex;
	TransformVertices(vertexCount, minIndex, wvp * viewport);

	switch (primitive)
	{
		/*
		case PRIMITIVE_POINTS:
			DrawPoints(vertexCount);
			break;
		*/
		
		case PRIMITIVE_TRIANGLES:
		{
			for (UInt16 i = 0; i < indexCount; i += 3)
			{
				UInt16 base = i - minIndex;
				DrawTriangle(indices[base], indices[base + 1], indices[base + 2]);
			}
			
			break;
		}
	}
}

er::SoftwareRenderer::SoftwareRenderer(er::UInt32 width, er::UInt32 height) :
	m_width(width),
	m_height(height),
	m_colorMask(255, 255, 255, 255),
	m_positions(0),
	m_normals(0),
	m_texCoords(0)
{
}

er::SoftwareRenderer::~SoftwareRenderer()
{

}

bool er::SoftwareRenderer::Initialize()
{
	Resize(m_width, m_height);
	m_outputVerts.resize(1024 * 1024); // JBB_TODO: This is huge and needs to be adjusted.

	return true;
}

void er::SoftwareRenderer::TransformVertices(er::UInt32 count, er::UInt32 offset, const D3DXMATRIX &matrix)
{
	::D3DXVec3TransformCoordArray(reinterpret_cast<D3DXVECTOR3 *>(&m_outputVerts[0]), sizeof(D3DXVECTOR3),
		reinterpret_cast<const D3DXVECTOR3 *>(m_positions), sizeof(D3DXVECTOR3), &matrix, count);
}

void er::SoftwareRenderer::DrawPoints(er::UInt32 count)
{
	const D3DXVECTOR3 *vertex = reinterpret_cast<const D3DXVECTOR3 *>(&m_outputVerts[0]);

	for (UInt32 i = 0; i < count; ++i)
	{
		Int32 x = RoundToInt32(vertex->x);
		Int32 y = RoundToInt32(vertex->y);
		
		if (x >= 0 && x < static_cast<Int32>(m_width) && y >= 0 && y < static_cast<Int32>(m_height))
		{
			Color *output = &m_colorBuffer[y * m_width + x];
			output->r = 255;
			output->g = 255;
			output->b = 255;
			output->a = 255;
		}
		
		++vertex;
	}
}

void er::SoftwareRenderer::DrawTriangle(er::UInt16 indexA, er::UInt16 indexB, er::UInt16 indexC)
{
	const D3DXVECTOR3 *vertices = reinterpret_cast<const D3DXVECTOR3 *>(&m_outputVerts[0]);

	const D3DXVECTOR3 &a = vertices[indexA];
	const D3DXVECTOR3 &b = vertices[indexB];
	const D3DXVECTOR3 &c = vertices[indexC];
	
	const D3DXVECTOR3 ab = b - a;
	const D3DXVECTOR3 ac = c - a;
	
	const D3DXVECTOR3 perp_ab(a.y - b.y, b.x - a.x, 0.0f);
	const D3DXVECTOR3 perp_ac(a.y - c.y, c.x - a.x, 0.0f);
	
	float minx = std::max(Round(std::min(std::min(a.x, b.x), c.x)) - 1.0f, 0.0f);
	float maxx = std::min(Round(std::max(std::max(a.x, b.x), c.x)) + 1.0f, m_widthf);
	float miny = std::max(Round(std::min(std::min(a.y, b.y), c.y)) - 1.0f, 0.0f);
	float maxy = std::min(Round(std::max(std::max(a.y, b.y), c.y)) + 1.0f, m_heightf);
	
	Int32 iminx = static_cast<Int32>(minx);
	Int32 imaxx = static_cast<Int32>(maxx);
	Int32 iminy = static_cast<Int32>(miny);
	Int32 imaxy = static_cast<Int32>(maxy);
	
	D3DXVECTOR3 p(minx + 0.5f, miny + 0.5f, 0.0f);

	float invBetaDenom  = 1.0f / D3DXVec3Dot(&perp_ac, &ab);
	float invGammaDenom = 1.0f / D3DXVec3Dot(&perp_ab, &ac);
	
	for (Int32 i = iminy; i < imaxy; ++i)
	{
		for (Int32 j = iminx; j < imaxx; ++j)
		{
			D3DXVECTOR3 ap = p - a;
		
			float beta  = D3DXVec3Dot(&perp_ac, &ap) * invBetaDenom;
			float gamma = D3DXVec3Dot(&perp_ab, &ap) * invGammaDenom;
			float alpha = 1.0f - beta - gamma;
			
			if (0.0f < alpha && alpha < 1.0f &&
			    0.0f < beta  && beta  < 1.0f &&
			    0.0f < gamma && gamma < 1.0f)
			{
				Int32 offset = i * m_width + j;
				float newDepth = a.z * alpha + b.z * beta + c.z * gamma;
				float *oldDepth = &m_depthBuffer[offset];
				
				if (newDepth <= *oldDepth && newDepth >= 0.0f)
				{
					*oldDepth = newDepth;
			
					Color *output = &m_colorBuffer[offset];
					output->r = static_cast<UInt8>(255.0f * alpha);
					//output->r += 10 & m_colorMask.r;
					output->g = static_cast<UInt8>(255.0f * beta);
					output->b = static_cast<UInt8>(255.0f * gamma);
					output->a = 255;
				}
			}
			
			p.x += 1.0f;
		}
		
		p.x = minx + 0.5f;
		p.y += 1.0f;
	} 
}
